/*->c.sp     */

/* Ovation!   (c) D. J. Pilling,  December 1988                     */
/*                        Spelling Checker                            */


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <ctype.h>
#include <time.h>

#include "h.os"
#include "h.wimp"
#include "h.sprite"
#include "h.font"
#include "h.bbc"


#include "h.def"

#include "h.wos"
#include "h.ram"
#include "h.file"
#include "h.vm"
#include "h.main"

#include "h.cross"

#include "h.dc"
#include "h.sp"




/*****************************************************************************/
                            /* Edit a dictionary */



dline dtable[MAXNAMES];

int   totnames=0;                 /* number of dictionaries     */

char  dcmap[MAXNAMES];            /* dictionaries for file      */
int   nodc;

#define DMSIZE 0x80

int   dictm[DMSIZE];




void opencheck(void);
void toggleguessmode(void);
void stepnextdict(void);


/****************************************************************************/
                    /* concerned with edit window */

#define WDHEIGHT  32
#define WDWIDTH   480
#define WDMARGIN  0
#define WDXMARGIN 4
#define WDSHIFT   4

/* overall width of words window              */
#define WORDWIDTH   508

/* height of words window for check story     */
#define WORDCHEIGHT 32*5

/* height of words window for edit dictionary */ 
#define WORDEHEIGHT 32*7


dict * editd;
int    editno;
int    wselec;


char   edstring[STRLEN];
char   guessit[STRLEN];


int     emode=0;     /* what mode is the edit window in */


#define NOMODE 0
#define EDIT   1
#define CHECKS 2


dict guessdc={0,0,NULL,NULL};  /* dictionary to put guesses in     */
dict scrapdc={0,0,NULL,NULL};  /* dictionary to put trash   in     */


int guessmode=1;   /* if we are in guess mode          */
int curdict;       /* current dictionary in file map   */


/*****************************************************************************/

void setwext(void)
{
 int minx,miny;
 getw(whandle[WORDS]); 
 minx=x1-x0;

 if(emode==EDIT) miny=WORDEHEIGHT;
 else            miny=WORDCHEIGHT;

 if(miny<(nowords(editd)*(WDHEIGHT+WDMARGIN)+WDMARGIN))
                 miny=nowords(editd)*(WDHEIGHT+WDMARGIN)+WDMARGIN;  
 extent(whandle[WORDS],0,-miny,minx,0);
}


/* write title */

void writewtitle(char * title)
{
 wimp_winfo winfo;
 winfo.w=whandle[WORDS];
 wimp_get_wind_info(&winfo);
 strcpy(winfo.info.title.indirecttext.buffer,title);
}


void openewordsn(int n)
{
 wimp_wstate wblock;

 wimp_get_wind_state(whandle[WORDS],&wblock);

 wblock.o.y=-(n+1)*(WDMARGIN+WDHEIGHT)+(WDHEIGHT>>1)+
             ((wblock.o.box.y1-wblock.o.box.y0)>>1);

 wimp_open_wind(&wblock.o);
}


void setsmart9(void)
{
 bbc_vduq(23,145,12,4,4,0,0,0,0,0);
}


void stripstring(char * dest,char * src)
{
 int c;

 do
 {
  c=*src++;



  *dest++=c;
 } while(c);
}



/*****************************************************************************/
/* called by redwords to redraw edit words window */


/*
void redewords(void)
{
 int more;
 int ylo,yhi;
 int n,y,now;
 int ox,oy;
 wimp_redrawstr redrawstr;
 word words;

 now=nowords(editd);

 redrawstr.w=ewindow;
 getw(ewindow);

 wimp_redraw_wind(&redrawstr,&more);
 while(more)
 {
  if(now)
  {
   ox=redrawstr.box.x0-redrawstr.scx;
   oy=redrawstr.box.y1-redrawstr.scy;
   ylo=-(redrawstr.g.y1-oy);
   yhi=-(redrawstr.g.y0-oy);

   n=(ylo-WDMARGIN)/(WDHEIGHT+WDMARGIN);
   y=n*(WDHEIGHT+WDMARGIN)+WDMARGIN+WDSHIFT;

   if(locword(n,editd,&words))
   {
    while(y<=yhi && n<now)
    {
     if(n==wselec)
     {
      wimp_setcolour(7);
      bbc_rectanglefill(ox+WDXMARGIN,oy-y,WDWIDTH,-WDHEIGHT);
      wimp_setcolour(0);
     }
     else
     wimp_setcolour(7);

     bbc_move(ox+WDXMARGIN,oy-y);
     bbc_stringprint(words.string);
     y+=WDHEIGHT+WDMARGIN;
     n++;
     nextword(editd,&words);
    }
   }
  } 
  wimp_get_rectangle(&redrawstr,&more);
 }
}
*/

void redewords(void)
{
 int more;
 int ylo,yhi;
 int n,y,now;
 int ox,oy;
 wimp_redrawstr redrawstr;
 word words;

 now=nowords(editd);

 redrawstr.w=ewindow;
 getw(ewindow);

 wimpfontstart();

 wimp_redraw_wind(&redrawstr,&more);
 while(more)
 {
  if(now)
  {
   ox=redrawstr.box.x0-redrawstr.scx;
   oy=redrawstr.box.y1-redrawstr.scy;
   ylo=-(redrawstr.g.y1-oy);
   yhi=-(redrawstr.g.y0-oy);

   n=(ylo-WDMARGIN)/(WDHEIGHT+WDMARGIN);
   y=n*(WDHEIGHT+WDMARGIN)+WDMARGIN+WDSHIFT;

   if(locword(n,editd,&words))
   {
    while(y<=yhi && n<now)
    {
     if(n==wselec)
     {
      wimp_setcolour(7);
      bbc_rectanglefill(ox+WDXMARGIN,oy-y,WDWIDTH,-WDHEIGHT);
      wimp_setcolour(0);
     }
   /*  else
     wimp_setcolour(7); */

   /*  bbc_move(ox+WDXMARGIN,oy-y);
     bbc_stringprint(words.string); */ /* printf("%d",n); */

     stringat(ox+WDXMARGIN,oy-y,words.string,7<<(8*(n==wselec)),WDHEIGHT);

     y+=WDHEIGHT+WDMARGIN;
     n++;
     nextword(editd,&words);
    }
   }
  } 
  wimp_get_rectangle(&redrawstr,&more);
 }

 wimpfontend();
}






void refreshwords(int n)
{
 wimp_redrawstr r;
 getw(whandle[WORDS]);
 r.box.x0=0;
 r.box.x1=x1-x0;

 if(n<0)
 {
  r.box.y1=scy;
  r.box.y0=r.box.y1-(y1-y0);
 }
 else
 {
  r.box.y1=-n*(WDHEIGHT+WDMARGIN)-WDMARGIN-WDSHIFT+2*deltay;
  r.box.y0=r.box.y1-WDHEIGHT-2*deltay;
 }

 r.w=whandle[WORDS];
 wimp_force_redraw(&r);
}


/* words pane clicks */

void ewordsicon(void)
{
 int n,y;

 int handle;


 if(emode==EDIT)
 {
  handle=whandle[EDITD];
  if(buttons & 0x2)
  {
   popsavedc();
   return;
  }
 }
 else
  handle=whandle[CHECK];


 if(buttons & 0xF00)    /* single click */
 {
  getw(whandle[WORDS]);
  y=-(mousey-by);  /* +ve number */

  /* map y into icon number */

  n=wselec;
  wselec=(y-WDMARGIN)/(WDHEIGHT+WDMARGIN);
 
  if(wselec==n) return;
  if(wselec>=nowords(editd) || wselec<0) {wselec=n;return;}

  /* redraw selected icon   */
  if(n>=0) refreshwords(n);

  /* redraw new icon        */
  refreshwords(wselec);

  writeicon(handle,3,wordn(wselec,editd));

  findcaret();
  if(chandle==handle) iecarrot(handle,3);
 }

}



/* open words pane */

void openewords(wimp_openstr * open)
{
 wimp_openstr o;
 int w,h;

 getw(whandle[WORDS]);
 w=x1-x0;
 h=y1-y0;

 x0=open->box.x0+18;
 x1=x0+WORDWIDTH;
 y1=open->box.y1-52; 
 y0=y1-WORDEHEIGHT;

 o.w = whandle[WORDS];
 o.box.x0 = x0;
 o.box.y0 = y0;   
 o.box.x1 = x1;
 o.box.y1 = y1;
 o.x = scx;       
 o.y = scy;
               
 o.behind = open->behind;
 wimp_open_wind(&o);
}



void wopeneditd(wimp_openstr * wopen)
{
 int left;
 getw(whandle[EDITD]);
 left=(x0>wopen->box.x0);
 getw(whandle[WORDS]);
 if(!(wflags & 0x10000) || left) openewords(wopen);
 if(bhandle==wopen->behind) wopen->behind=whandle[WORDS];
 wimp_open_wind(wopen);
 if(wopen->behind==-2)
  {
   getw(whandle[EDITD]);
   wopen->behind=bhandle;
  }
 openewords(wopen);
}


void seteditextent(void)
{
 setwext();
 if(emode==EDIT) writeiconf(whandle[EDITD],5,"%d",nowords(editd));
}



void savechanges(void)
{
 int i;
 for(i=0;i<totnames;i++) 
          if(dtable[i].modded)
           {
            savedc(dtable[i].name,&dtable[i].dictp);
            dtable[i].modded=0;
           }
}


void scrapchanges(void)
{
 int i;
 for(i=0;i<totnames;i++) 
          if(dtable[i].modded)
           {
            trashdc(&dtable[i].dictp);
            loaddcn(i);
            dtable[i].modded=0;
           }
}


int changes(void)
{
 int i;
 for(i=0;i<totnames;i++) if(dtable[i].modded) return(1);
 return(0);
}


void closeeditd(int ok)
{ 
 if(ok)
 {      /* stash stuff  */
  savechanges();
 }
 else
 {
  scrapchanges();
 }

 closedownt(EDITD);
 closedownt(WORDS);
 remzeroevent(DCDEMON);
 emode=NOMODE;
}


void adddictword(int dno)
{
 int handle;
 char buff[64];

 if(emode==EDIT) handle=whandle[EDITD];
 else            handle=whandle[CHECK];

 stripstring(buff,iconaddr(handle,3));

 dtable[dno].modded|=addword(buff,&dtable[dno].dictp);

 if(emode==CHECKS) makeguess(guessit);

 if(emode==EDIT || !guessmode)
 {
  seteditextent();
  refreshwords(-1);           
  editdemon(1);
 }
 else
 if(emode==CHECKS && guessmode)
 {
  setwext();
  refreshwords(-1);
 }
}






/* main window clicks */

void editdicon(void)
{
 if(buttons & 0x2) popsavedc();
 else
 switch(icon)
 {
  case 1:
  case 2:
         closeeditd(icon==2);
         break;

  case 4:                                           /* add word */
         adddictword(editno);
         break;

  case 0:                                           /* delete word */
         if(wselec>=0 && wselec<nowords(editd))
                    dtable[editno].modded|=deleteword(edstring,editd);
         seteditextent();
         refreshwords(-1);
         editdemon(1);
         break;
 }
}



void editdkey(int * key) 
{
 if(*key==13)
 {                                           /* add word to dictionary */
  adddictword(editno);
 }
 else
 if(*key==27) closeeditd(0);
 else return;
 *key=-1;
}


void popupeditd(void)
{
 wimp_wstate wst;
 centerwindow(&wst,whandle[EDITD],0);
 wopeneditd(&wst.o);
 iecarrot(whandle[EDITD],3);
}


void openeditd(int no,dict * dp,char * title)
{
 if(!createwindow(WORDS)) return;
 if(!createwindow(EDITD))
 {
  closedownt(WORDS);
  return;
 }

 editd=dp;
 editno=no;
 emode=EDIT;

 setsmart9();

 writewtitle(title);

 writeicon(whandle[EDITD],3,"");
 edstring[0]=0;
 seteditextent();

 wselec=-1;

 popupeditd();

 addzeroevent(DCDEMON);
}



/**************************************************************************/

/* called from zero when words window is open */
/* if word icon has changed, reopen window at appropriate point */

void editdemon(int force)
{
 int  found;
 int  n;
 int  ntemp;
 int  handle;
 int  flags=0;
 word words;
 char buff[64];

 if(emode==EDIT) handle=whandle[EDITD];
 else            handle=whandle[CHECK];

 if(strcmp(edstring,iconaddr(handle,3)) || force)
 {
  strcpy(edstring,iconaddr(handle,3));

  stripstring(buff,edstring);

  found=findword(buff,editd,&words,&flags);
  if(flags & 0x1) found=0;
  n=words.wordn;

  if(found && n==wselec) return;
  openewordsn(n);
  if(wselec>=0) {ntemp=wselec;wselec=-1;refreshwords(ntemp);}

  if(found)
  {
   wselec=n;
   refreshwords(wselec);
  }
 }
}





/****************************************************************************/


/* a file of words has been dropped onto the edit window */

int readdc(char * name)
{
 FILE  * fp;
 int c;
 char string[STRLEN];
 int i;

 if(emode!=EDIT) return(0);

 fp=ropen(name,"rb");
 if(!fp) return(0);

 hourglasson();

 while(!rameof(fp))
 {
  while(1)
  {
   c=rgetc(fp);
   if(rameof(fp) || c>32) break;
  }
  if(rameof(fp) && c<=32) break;

  i=0;
  string[i++]=c;

  while(!rameof(fp) && i<STRLEN) 
  {
   c=rgetc(fp);
   if(c<32) break;
   if(isalpha(c)) string[i++]=tolower(c);
  } 

  if(i<STRLEN)
  {
   string[i]=0;
   dtable[editno].modded|=addword(string,editd);
  }

 }

 rclose(fp);

 seteditextent();
 refreshwords(-1);

 hourglassoff();

 return(1);
}


void popsavedc(void)
{
 int handle;

 handle=setsave(SAVEDTEXT);
 menuwindow(handle);
}


/* save dictionary as text */
                  
int writedc(char * name)
{
 FILE * fp;
 word   words;

 if(!nowords(editd)) return(0);

 fp=ropen(name,"wb");
 if(!fp) return(0);

 locword(0,editd,&words);
 do
 {
  rwrite(words.string,strlen(words.string),1,fp);
  rputc(10,fp);
 } while(nextword(editd,&words));

 savetypeclose(fp,name);

 closedownt(SAVEFILE0);

 return(1); /* success */
}


/****************************************************************************/


void writedm(int * smp,char * string,int bits)
{
 *(smp+0)=bits;
 *(smp+1)=-1;
 *(smp+2)=0x7000021;
 strcpy((char *)(smp+3),string);
}


int * setdictmsubtag(char * name,int mode)
{
 int *  dictp;
 int    i;
 int    j;
 int    tick;


 for(i=0;i<DMSIZE;i++) dictm[i]=0;

 strcpy((char *)dictm,name);
 *(((char *)dictm)+12)=7;
 *(((char *)dictm)+13)=2;
 *(((char *)dictm)+14)=7;
 *(((char *)dictm)+15)=0;
 dictm[4]=12+16*11;
 dictm[5]=40;
 dictm[6]=0;

 dictp=dictm+7;

 for(i=0;i<totnames;i++)
 {
  tick=0;

  if(mode)
  {
   for(j=0;j<nodc;j++)
    if(dcmap[j]==i)
    {
     tick=1;
     break;
   }
  }

  writedm(dictp,dtable[i].name,tick);

  dictp+=6;
  if((dictp-dictm)>=(DMSIZE-6)) break;
 }

 *(dictp-6)|=0x80;

 return(dictm);
}



/* called by dynamic menu code to set up dict name menus */

int * setdictm(int m3)
{

 switch(m3)
 {
  case 0:
         return(setdictmsubtag("Load",1));  /* Load */
         break;

  case 1:
         return(setdictmsubtag("Edit",0));  /* Edit */
         break;

  case 3:
         return(setdictmsubtag("Delete",0));  /* Delete */
         break;
 }

 return(NULL);
}



/*****************************************************************************/

void initline(int line)
{
 int i;

 for(i=0;i<12;i++) dtable[line].name[i]=0;

 dtable[line].dictp.nowords=0;
 dtable[line].dictp.noindex=0;
 dtable[line].dictp.words=NULL;
 dtable[line].dictp.index=NULL;
 dtable[line].loaded=0;
 dtable[line].uses=0;
 dtable[line].modded=0;
}


void inittable(void)
{
 int line;
 for(line=0;line<MAXNAMES;line++) initline(line);
 totnames=0;
}



/* loads the dictionary names */

void loadnames(void)
{
 fstat      f;

 /* scan the dictionary directory */

 totnames=0;
 startscan();
 while(nextitem("<CrossStarDictionary$Path>.Dictionary",&f,"*"))
 {
  strcpy(dtable[totnames].name,f.name);
  totnames++;
  if(totnames>=MAXNAMES) break;
 }
}


/* given a dictionary number - load the dictionary */

int loaddcn(int n)
{
 int code;

 code=loaddc(dtable[n].name,&dtable[n].dictp);

 if(code) dtable[n].loaded=1;
 else     dtable[n].loaded=0;

/* testdict(&dtable[n].dictp); */

 return(code);
}


/* given a dictionary number - remove the dictionary from memory */

int unloaddcn(int n)
{
 trashdc(&dtable[n].dictp);
 dtable[n].loaded=0;
 return(1);
}


void dccleanup(void)
{
 int i;
 for(i=0;i<totnames;i++) 
          if(!dtable[i].uses && dtable[i].loaded) unloaddcn(i);
}



/* given a dictionary name, return its number or -1 */

int finddc(char * name)
{
 int i=0;
 while(i<totnames) if(!cstrcmp(dtable[i].name,name)) return(i); else i++;
 return(-1);
}


/* how many dictionaries are there? */

int nodcs(void)
{
 return(totnames);
}


/* how many dictionaries are there excluding hyphenation dictionary */

int nodcsexhp(void)
{
 return(totnames);
}


/*****************************************************************************/
           /* Edit dictionary menu - load,edit,create,delete */


void dcadd(int n)
{
 int i;
 int j;

 for(i=0;i<nodc;i++) if(dcmap[i]>=n) break;

 if(nodc==MAXNAMES)
 {
  errorbox("Cannot load anymore dictionaries for this document.");
  return;
 }

 for(j=nodc+1;j>i;j--) dcmap[j]=dcmap[j-1];

 dcmap[i]=n;
 nodc++;
 dtable[n].uses++;
}


void dcaddload(int n)
{
 dcadd(n);
 if(!dtable[n].loaded) loaddcn(n);
}



/* remove a dictionary from a file */

void dcrem(int n)
{
 int i;
 int j;

 for(i=0;i<nodc;i++) if(dcmap[i]>=n) break;

 if(nodc && i<nodc && dcmap[i]==n)
 {
  for(j=i;j<(nodc-1);j++) dcmap[j]=dcmap[j+1];

  nodc--;
  dtable[n].uses--;

  if(!dtable[n].uses && dtable[n].loaded) unloaddcn(n);
 }
}


/* does file use this dictionary ? */

int dcfile(int n)
{
 int i;
 for(i=0;i<nodc;i++) if(dcmap[i]==n) return(1);
 return(0);
}



/* add the dictionary to the current document */

void loaddict(int m3)
{
 if(dcfile(m3)) dcrem(m3);
 else           dcaddload(m3);
}



void editdict(int m3)
{
 int code;
 
 if(!dtable[m3].loaded) code=loaddcn(m3);
 else                   code=1;

 if(code)
 {
  openeditd(m3,&dtable[m3].dictp,dtable[m3].name);
 }
}



void dcins(int n)
{
 int j;

 for(j=0;j<nodc;j++) if(dcmap[j]>=n) dcmap[j]++;
}



/* called from menu to create a dictionary */

void createdict(char * string)
{
 int i;
 int n;

 if(totnames==MAXNAMES)
 {
  errorbox("Cannot create anymore user dictionaries.");
  return;
 }

 for(i=0;i<totnames;i++)
 {
  if(!cstrcmp(dtable[i].name,string))
  {
   errorbox("Dictionary already exists.");
   return;
  }
 }


 for(n=0;n<totnames;n++)
    if(strcmp(dtable[n].name,string)>0) break;


 totnames++;
 for(i=totnames-1;i>n;i--) 
 {
  dtable[i]=dtable[i-1];
  relocanchor(&dtable[i-1].dictp.words,&dtable[i].dictp.words);
  relocanchor(&dtable[i-1].dictp.index,&dtable[i].dictp.index);
 }

 initline(n);
 strcpy(dtable[n].name,string);
 dtable[n].loaded=1;

 dcins(n);

 createdc(&dtable[n].dictp);
 if(!savedc(dtable[n].name,&dtable[n].dictp))
 {
  deletedictsub(n);
  errorbox("Cannot create dictionary");
 }
}



void dcdel(int n)
{
 int j;
 dcrem(n);
 for(j=0;j<nodc;j++) if(dcmap[j]>n) dcmap[j]--;
}


void deletedictsub(int m3)
{
 char string[128];
 int  i;

 strcpy(string,"Delete <CrossStarDictionary$Path>.Dictionary.");
 strcat(string,dtable[m3].name);
 os_cli(string);

 dcdel(m3);

 for(i=m3;i<(totnames-1);i++) 
    {
     dtable[i]=dtable[i+1];
     relocanchor(&dtable[i+1].dictp.words,&dtable[i].dictp.words);
     relocanchor(&dtable[i+1].dictp.index,&dtable[i].dictp.index);
    }

 totnames--;
}



void deletedict(int m3)
{
 if(confirm("Deleting Dictionary, Are You Sure?")!=1) return;  
 deletedictsub(m3);
}


/*************************************************************************/
             /* handle dictionary loading within documents */



/* return 1 if any errors */
int readfhdc(FILE * fp)
{
 char name[12];
 int  dcn;

 nodc=0;

 while(1)
 {
  rread(name,12,1,fp);
  if(rerror(fp)) return(1);
  if(!strlen(name)) break;
  dcn=finddc(name);
  if(dcn>-1) dcadd(dcn);
 }

 dccleanup();

 return(0);
}


/* return 1 if any errors */

int writefhdc(FILE * fp)
{
 int i;
 char name[12];

 for(i=0;i<nodc;i++) 
          rwrite(dtable[dcmap[i]].name,12,1,fp);

 for(i=0;i<12;i++) name[i]=0;
 rwrite(name,12,1,fp);

 if(rerror(fp)) return(1);
 return(0);
}


void mapfhdc(void)
{
 int i;

 for(i=0;i<nodc;i++) 
     if(!dtable[dcmap[i]].loaded) loaddcn(dcmap[i]);
}



void newfhdcmap(void)
{
 nodc=0;
}



void trashfhdc(void)
{
 int i;
 for(i=0;i<nodc;i++) dtable[dcmap[i]].uses--;
 nodc=0;
}


/*****************************************************************************/


char * swordn(int word)
{
 int dc=word>>24;
 char * w=(wordn(word & 0xFFFFFF,&dtable[dc].dictp));
 return(w);
}




#define MSPLIT 3


void missingletter(char * string,dict * dp,word * words)
{
 char * c;
 int    n;
 int    match=32;
 int    miss1;
 int    miss2;
 int    offset;
 char   work[32];
 char   * w;
 char   * s;

 offset=words->woffset+words->len;
 strcpy(work,words->string);
 c=charbuffer(dp->words,offset);

 n=*c;
 while(1)
 {
  work[n]=0;

  if(n>(match+1))
  {
   while(1)
   {
    c++;
    if((n=*c)<MINTOK) break;
   }
  }
  else
  {
   while(1)
   {
    c++;
    if((n=*c)<MINTOK) break;
    strcat(work,mfx[n]);
   }

   match=miss1=miss2=0;
   w=work;
   s=string;

   while(1)
   {
    if(*w!=*s && toupper(*w)!=toupper(*s))
    {
/*     if(!*s) break;
     if(!*w) break;  */
  
     if(miss1 && miss2) break;
     else
     if(!miss1 && *w && *(w+1)==*s) {miss1=1;w++;match++;}
     else
     if(!miss2 && *s && *w==*(s+1)) {miss2=1;s++;}
     else
     if(!miss1 && !miss2) {miss1=miss2=1;w++;s++;match++;}
     else
     break;
    }
    else
    if(!*w)
    {
     offset=c-charbuffer(dp->words,0);
     addword(work,&guessdc);
     c=charbuffer(dp->words,offset);
     break;
    }
    else
    {
     match++;
     w++;
     s++;
    }
   }

  }

  if(match<MSPLIT) break;
 }

}


void makeguess2(char * string)
{
 char temp[64];
 word words;
 int  len=strlen(string);
 int  lenx;
 int  i;
 int  j;
 int  k;
 int  flags=0;

 /* closest in dictionary */

 for(i=0;i<nodc;i++)
 {
  findword(string,&dtable[dcmap[i]].dictp,&words,&flags);
  if(toupper(string[0])==toupper(words.string[0]))
                                         addword(words.string,&guessdc);
  else
  {
   nextword(&dtable[dcmap[i]].dictp,&words);
   if(toupper(string[0])==toupper(words.string[0]))
                                         addword(words.string,&guessdc);
  }
 }


 /* permutations of letters */

 flags=0x20000;

 for(j=0;j<len-1;j++)
 {
  strcpy(temp,string);
  temp[j]=temp[j+1];
  temp[j+1]=string[j];

  for(i=0;i<nodc;i++)
    if(findword(temp,&dtable[dcmap[i]].dictp,&words,&flags))
     {
      xgetscanstring(words.string);
      addword(words.string,&guessdc);
      break;
     }
 }


 /* missing letters out */

 if(len>1)
   for(j=0;j<len;j++)
   {
    for(i=0;i<=len;i++) temp[i-(i>j)]=string[i];

    for(i=0;i<nodc;i++)
     if(findword(temp,&dtable[dcmap[i]].dictp,&words,&flags))
     {
      xgetscanstring(words.string);
      addword(words.string,&guessdc);
      break;
     }
   }


 /* adding extra letters */

 if(len>MSPLIT) lenx=MSPLIT; else lenx=len;

 for(k=0;k<=lenx;k++)
 {
  for(j=0;j<=len;j++) temp[j+(j>=k)]=string[j];

  for(j='a';j<='z';j++)
  {
   temp[k]=j;
   for(i=0;i<nodc;i++)
   if(findword(temp,&dtable[dcmap[i]].dictp,&words,&flags))
   {
    xgetscanstring(words.string);
    addword(words.string,&guessdc);
   }
  }
 }


 /* wrong letters */

 for(k=1;k<=lenx;k++)
 {
  strcpy(temp,string);

  for(j='a';j<='z';j++)
  {
   temp[k]=j;
   for(i=0;i<nodc;i++)
   if(findword(temp,&dtable[dcmap[i]].dictp,&words,&flags))
   {
    xgetscanstring(words.string);
    addword(words.string,&guessdc);
   }
  }
 }



 flags=0;

 if(len>MSPLIT)
 {
  strcpy(temp,string);
  temp[MSPLIT]=0;

  for(i=0;i<nodc;i++)
  {
   findword(temp,&dtable[dcmap[i]].dictp,&words,&flags);
   missingletter(string,&dtable[dcmap[i]].dictp,&words);
  }
 }

}


/* make guesses for word */

void makeguess(char * string)
{
 strcpy(guessit,string);
 trashdc(&guessdc);
 createdc(&guessdc);
 makeguess2(string);
}

/*****************************************************************************/

/* argument is a string stripped of smart quotes and soft hyphens */
/* assumes true string is held in edstring                        */
/* return 0 if gone past end of visible text                      */

int spellerror(char * string)
{


 makeguess(string);

 if(emode==NOMODE)
 {
  opencheck();
 }

 writeicon(whandle[CHECK],3,edstring);
 iecarrot(whandle[CHECK],3);

 if(!guessmode)    editdemon(1);
 else
 if(emode!=NOMODE)
 {
  wselec=-1;
  setwext();
  openewordsn(0);
  refreshwords(-1);
 }

 emode=CHECKS;

 return(1);
}




/*

The rules for capitalization are as follows:

Any word may appear in all capitals, as in headings.

Any word that is in the dictionary in all-lowercase form may appear
either in lowercase or capitalized (as at the beginning of a sentence).

Any word that has "funny" capitalization (i.e., it contains both cases
and there is an uppercase character besides the first) must appear
exactly as in the dictionary, except as permitted by rule (1).
If the word is acceptable in all-lowercase, it must appear thus in a
dictionary entry.

*/


int spellcheckwordsub(char * string)
{
 word words;
 int  i;
 int  flags=0x20000;

 for(i=0;i<nodc;i++)
 {
  if(findword(string,&dtable[dcmap[i]].dictp,&words,&flags))
  {
   if(!(flags & 0x1)) return(1);  /* case matched */
  }
 }
 return(0);
}





/* wrapped up spell check, handles multiple file dictionaries */

int spellcheckword(char * string1)
{
 return(spellcheckwordsub(string1));
}

int cluenumber;


/* return 2 word OK - close check, 1 word OK or 0 error */

int checknextsub(int oneshot,int checker)
{
 char   string[64];
 int    i;

 while(nextclue(string,cluenumber++))
 {
  i=0;
  while(string[i])
  {
   if(string[i]==32) break;
   else i++;
  }

  if(!string[i])   /* don't check words with spaces in */
  {
   strcpy(edstring,string);

   if(!spellcheckword(string)) 
   {
    if(checker)
    {
     if(!spellerror(string)) return(2);
    }
    return(0);
   }
  }
 }

 return(2);
}


int checknext(int oneshot,int checker)
{
 int code;
 hourglasson();
 code=checknextsub(oneshot,checker);
 hourglassoff();
 if(code==2 || code==3) 
 {
  if(emode==CHECKS) closecheck(1);
  else
  if(code==2) messagebox("No new words!");
  code=1;
 }
 return(code);
}




void checkcont(void)
{
 checknext(0,1);
}





int checkfiledc(void)
{
 int dcno;

 if(!nodc)
 {
  dcno=finddc("!MainDict");
  if(dcno!=-1)
  {
   dcaddload(dcno);
   /* modfile(); */
  }
  if(!nodc)
  {
   errorbox("No dictionaries loaded!");
   return(1);
  }
 }
 return(0);
}



/* check current source */

void spellcheck(void)
{
 cluenumber=0;
 if(checkfiledc()) return;
 checknext(0,1);
}




/****************************************************************************/







int findwordx1(char * string,int dc,word * words,int firstspace,
                                                            int len,int clue)
{
 int         n;
 int         lo;
 int         hi;
 int         i;
 int         loi;
 int         hii;
 int         ii;
 int         code;
 index    *  f;
 char     *  p;
 word        oldwords;
 int         flags=0;
 dict     *  dp;
 int         check;

 dp=&dtable[dcmap[dc]].dictp;
 n=dp->noindex;

 lo=0;
 hi=(n-1)*sizeof(index);
 i=0;
 loi=0;
 hii=n-1;
 ii=0;
 check=0;

 if(!dp->nowords)
 {
  words->woffset=0;
  words->indexn=0;
  strcpy(words->string,"");
  words->wordn=0;
  words->len=0;
 }
 else
 {
  p=charbuffer(dp->index,0);

  if(firstspace>KEYLEN) firstspace=KEYLEN;

  while(hi>=lo)
  {
   i=(lo+hi)>>1;
   ii=loi+hii;
   if((ii & 1) && i) i-=(sizeof(index)>>1);
   ii=ii>>1;

   f=(index *)(p+i);

   code=xstrncmp(string,f->key,firstspace,&flags);

   if(!code) break;
   else
   if(code>0) {lo=i+sizeof(index);loi=ii+1;}
   else       {hi=i-sizeof(index);hii=ii-1;}
  }

  while((code<=0) && (i>0))
  {
   i-=sizeof(index);
   ii--;
   f=(index *)(p+i);
   code=xstrncmp(string,f->key,firstspace,&flags);
  }

  strncpy(words->string,f->key,KEYLEN);
  words->woffset=WOFF(f);
  words->indexn=ii;
  words->wordn=WORDN(f);
  words->len=0;

  formword(dp,words);

 /* oldwords=*words; */

  while(1)
  {
   if(len==strlen(words->string))
   {
    for(i=0;i<len;i++)
    {
     if(string[i]!=' ' && tolower(words->string[i])!=tolower(string[i])) break;
     else
     if(words->string[i] && !string[i]) break;
    }
 
    if(i==len && !string[i])
    {
     cluestr[clue].dcdata=(dcmap[dc]<<24)+words->wordn;
  
     if(clue==(solwidth-1))
     {
      for(i=clue;i>=0;i--)
        addsolution(cluestr[i].dcdata,i);
     }
     else
     {
      fillinstring(clue,words->string);
      check=search(clue+1);
      fillinstring(clue,string);
     }
    }
    else
    if(i<firstspace)
    {
     code=xstrncmp(string,words->string,firstspace,&flags);
     if(code<0) break;
    }
   }

/* oldwords=*words; */

   if(!nextword(dp,words)) break;

   if((check>=0) && (check++==2048))
   {
    check=0;
    if(bbc_inkey(0)==27)
    {
     check=-1;
     break;
    }
   }
   else
   if(check<0) break;

  }

 /* *words=oldwords; */
 }
 return(check);
}














int search(int clue)
{
 word words;
 dict * dp;
 int  dc;
 int  len;
 int  i;
 char string[STRLEN];
 int  firstalpha;
 int  firstspace;
 int  flags=0;
 int  check;

 gridgetstring(clue,string);
 len=strlen(string);
 hourglasson();
 check=0;

 firstalpha=-1;
 firstspace=-1;

 for(i=0;i<len;i++)
 {
  if(isalpha(string[i]) && firstalpha<0) firstalpha=i;
  else
  if(string[i]==32 && firstspace<0)      firstspace=i;
 }

 for(dc=0;dc<nodc;dc++)
 {
  dp=&dtable[dcmap[dc]].dictp;

  if(firstspace==-1)    /* special case, no spaces, can just do a look up */
  {
   if(findword(string,dp,&words,&flags))
   {
    cluestr[clue].dcdata=(dcmap[dc]<<24)+words.wordn;

    if(clue==(solwidth-1))
    {
     for(i=clue;i>=0;i--)
       addsolution(cluestr[i].dcdata,i);
    }
    else
    check=search(clue+1);
   }
  }
  else
  if(firstalpha<firstspace)
  {                   /* have some alpha data to look up */
   check=findwordx1(string,dc,&words,firstspace,len,clue);

   if((check>=0) && (check++>8))
   {
    check=0;
    if(bbc_inkey(0)==27)
    {
     check=-1;
     break;
    }
   }
   else if(check<0) break;

  }
  else
  {                    /* hard case, got to scan the lot */
   locword(0,dp,&words);
   do
   {
    if(len==strlen(words.string))
    {
     for(i=0;i<len;i++)
     {
      if(string[i]!=' ' && words.string[i]!=string[i]) break;
      else
      if(words.string[i] && !string[i]) break;
     }
 
     if(i==len && !string[i])
     {
      cluestr[clue].dcdata=(dcmap[dc]<<24)+words.wordn;

      if(clue==(solwidth-1))
      {
       for(i=clue;i>=0;i--)
         addsolution(cluestr[i].dcdata,i);
      }
      else
      {
       fillinstring(clue,words.string);
       check=search(clue+1);
       fillinstring(clue,string);
      }
     }
    }

    if((check>=0) && (check++==2048))
    {
     check=0;
     if(bbc_inkey(0)==27)
     {
      check=-1;
      break;
     }
    }
    else if(check<0) break;

   } while(nextword(dp,&words));
   if(check<0) break;
  }
 }

 hourglassoff();
 return(check);
}



int cmpchar(const void * p1,const void * p2)
{
 return((*(char*)p2)-(*(char*)p1));
}


void anagramsearch(void)
{
 word   words;
 dict * dp;
 int    dc;
 int    len;
 int    i;
 int    j;
 int    ct;
 char   string[STRLEN];
 char   b[STRLEN];
 int    check;

 hourglasson();

 gridgetstring(0,string);
 len=strlen(string);

 for(i=0;i<len;i++) 
  if(string[i]==32)
  {
   errorbox("Can't have space in anagram");
   return;
  }

 qsort(string,len,1,cmpchar);

 check=0;

 for(dc=0;dc<nodc;dc++)
 {
  dp=&dtable[dcmap[dc]].dictp;

  locword(0,dp,&words);
  do
  {
   if(len==strlen(words.string))
   {
    strcpy(b,string);
    for(ct=0,i=0;i<len;i++)
    {
     if(ct!=i) break;
     for(j=0;j<len;j++)
      if(b[j]==words.string[i] || b[j]=='*')
      {
       b[j]=0;
       ct++;
       break;
      }
    }

    if(i==len && ct==len)
    {
     addsolution((dcmap[dc]<<24)+words.wordn,0);
    }
   }

   if(check++==2048)
   {
    check=0;
    if(bbc_inkey(0)==27)
    {
     check=-1;
     break;
    }
   }
  } while(nextword(dp,&words));
  if(check<0) break;
 }

 hourglassoff();
}


/*****************************************************************************/

/* open words pane */

void opencwords(wimp_openstr * open)
{
 wimp_openstr o;
 int w,h;

 getw(whandle[WORDS]);
 w=x1-x0;
 h=y1-y0;

 x0=open->box.x0+8;
 x1=x0+WORDWIDTH;
 y1=open->box.y1-52; 
 y0=y1-WORDCHEIGHT;

 o.w = whandle[WORDS];
 o.box.x0 = x0;
 o.box.y0 = y0;   
 o.box.x1 = x1;
 o.box.y1 = y1;
 o.x = scx;       
 o.y = scy;
               
 o.behind = open->behind;
 wimp_open_wind(&o);
}


void wopencheck(wimp_openstr * wopen)
{
 int left;
 getw(whandle[CHECK]);
 left=(x0>wopen->box.x0);
 getw(whandle[WORDS]);
 if(!(wflags & 0x10000) || left) opencwords(wopen);
 if(bhandle==wopen->behind) wopen->behind=whandle[WORDS];
 wimp_open_wind(wopen);
 if(wopen->behind==-2)
 {
  getw(whandle[CHECK]);
  wopen->behind=bhandle;
 }
 opencwords(wopen);
}


void popupcheck(void)
{
 wimp_wstate wst;
 centerwindow(&wst,whandle[CHECK],0);
 wopencheck(&wst.o);
}


void closecheck(int ok)
{ 
 if(!whandle[CHECK]) return;

 closedownt(CHECK);
 closedownt(WORDS);
 remzeroevent(DCDEMON);
 emode=NOMODE;

 if(changes())
 {
  ok=confirm("Dictionaries changed. Retain changes?");

  if(ok==1)
  {      /* stash stuff  */
   savechanges();
  }
  else
  {
   scrapchanges();
  }
 }

 trashdc(&guessdc);
 trashdc(&scrapdc);
}



void opencheck(void)
{
 int n;
 int handle;

 if(!createwindow(WORDS)) return;
 handle=createwindow(CHECK);
 if(!handle)
 {
  closedownt(WORDS);
  return;
 }

 curdict=0;

 n=dcmap[curdict];
 writeicon(handle,4,dtable[n].name);

 wselec=-1;

 if(guessmode)
 {
  editno=n;
  editd=&guessdc;
  writewtitle("Guess");
  remzeroevent(DCDEMON);
 }
 else
 {
  editd=&dtable[n].dictp;
  editno=n;
  writewtitle(dtable[n].name);
  addzeroevent(DCDEMON);
 }

 optst(handle,6,!guessmode);

 setwext();

 createdc(&scrapdc);

 setsmart9();

 popupcheck();
/* iecarrot(handle,3); can't put this here, because string not set up */
}



void setcheckwin(char * name,dict * dp,int n)
{
 editno=n;
 editd=dp;
 writewtitle(name);
 setwext();
 refreshwindowtitle(whandle[WORDS]);
 refreshwords(-1);
 openewordsn(0);
}




void goguessmode(void)
{
 if(guessmode) return;
 setcheckwin("Guess",&guessdc,dcmap[curdict]);
 guessmode=1;
 remzeroevent(DCDEMON);
 wselec=-1;
}




void godictmode(int full)
{
 int n;

 n=dcmap[curdict];
 writeicon(whandle[CHECK],4,dtable[n].name);
 if(full)
 {
  setcheckwin(dtable[n].name,&dtable[n].dictp,n);
  wselec=-1;
  addzeroevent(DCDEMON);
  guessmode=0;
  editdemon(1);
 }
}



void toggleguessmode(void)
{
 if(guessmode) godictmode(1);
 else          goguessmode();
 optst(whandle[CHECK],6,!guessmode);
}



void stepnextdict(void)
{
 if(nodc<=1) return;

 if(buttons & 0x1) {curdict--;if(curdict<0) curdict=nodc-1;}
 else
 if(buttons & 0x4) {curdict++;if(curdict>=nodc) curdict=0;}
 else
                   return;  /* Hmm. */

 godictmode(!guessmode);
}








void checkkey(int * key) 
{
 if(*key==13)
 {
  checkcont();
 }
 else
 if(*key==27) closecheck(0);
 else return;
 *key=-1;
}



/* main window clicks */

void checkicon(void)
{
  switch(icon)
  {

   case 0:        /* Cancel Spell Check */
          closecheck(1);
          break;

  case  1:        /* ignore word */
          checkcont();
          break;

       
   case 5:        /* dictionary */
          stepnextdict();
          break;


  case  2:        /* add word */
          adddictword(dcmap[curdict]);
          if(buttons & 0x1) checkcont();
          break;

   case 6:        /* toggle guess */
          toggleguessmode();
          break;

  }
}



/*****************************************************************************/


/* called from main to boot spell check code */

void initsp(void)
{
 inittable();
 loadnames();
}


